﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ListFiles;
using System.Runtime.InteropServices;
using System.Threading;
using System.Text.RegularExpressions;
using Microsoft.VisualBasic.CompilerServices;

namespace ListFiles
{
    public partial class Form1 : Form
    {
        // result is not protected by a lock because it's readonly after it has been initialized
        //Dictionary<UInt64, MasterFileTableEntry> result = new Dictionary<ulong, MasterFileTableEntry>();
        Dictionary<int, ListViewItem> searchCache = new Dictionary<int, ListViewItem>();
        ListViewItem[] cache;
        Volume mft = new Volume();
        System.Windows.Forms.Timer searchTimeout = new System.Windows.Forms.Timer();

        private delegate void OnLoadingDone(Volume mft);
        private event OnLoadingDone LoadingDone;

        private Dictionary<ulong, MasterFileTableEntry> result
        {
            get { return mft.Files; }
        }

        private Thread load;

        public Form1()
        {
            InitializeComponent();

            listView1.View = View.Details;
            listView1.VirtualMode = true;
            listView1.VirtualListSize = 0;

            listView1.Columns.Add("Name", 250);
            listView1.Columns.Add("Path", -2);

            listView1.RetrieveVirtualItem += new RetrieveVirtualItemEventHandler(listView1_RetrieveVirtualItem);
            listView1.CacheVirtualItems += new CacheVirtualItemsEventHandler(listView1_CacheVirtualItems);
            listView1.SearchForVirtualItem += new SearchForVirtualItemEventHandler(listView1_SearchForVirtualItem);

            searchTimeout.Tick += new EventHandler(searchTimeout_Tick);
            searchTimeout.Interval = 30;
            searchTimeout.Enabled = false;

            load = new Thread(
                () =>
                {
                    //if (!CompressedSerializer.CacheExists(@"res.txt"))
                    {
                        mft.EnumerateVolume();
                    }
                    //else
                    //{
                    //     CompressedSerializer.Read(result, @"res.txt");
                    //}

                    PrepopulateListviewCache();

                    if (LoadingDone != null)
                        LoadingDone(mft);
                });

            load.Start();
        }

        void searchTimeout_Tick(object sender, EventArgs e)
        {
            searchTimeout.Enabled = false;

            if (string.IsNullOrEmpty(textBox1.Text))
            {
                searchCache.Clear();
                listView1.VirtualListSize = cache.Length;
            }

            listView1.FindItemWithText(textBox1.Text);
        }

        bool WildcardMatch(string haystack, string needle)
        {
            if (needle == "*")
            {
                return true;
            }
            else if (needle[0] == '*')
            {
                return haystack.EndsWith(needle.Substring(1));
            }
            else if (needle.EndsWith("*"))
            {
                return haystack.StartsWith(needle.Substring(0, needle.Length - 1));
            }
            else if (needle.Contains('*'))
            {
                string[] parts = needle.Split(new char[] { '*' });
                bool result = true;

                foreach (string p in parts)
                    result &= haystack.Contains(p);

                return result;
            }

            return haystack.Contains(needle);
        }

        void listView1_SearchForVirtualItem(object sender, SearchForVirtualItemEventArgs e)
        {
            /*Regex re = null;

            try
            {
                // Occasionally it happens that during typing we enter an invalid
                // regular expression, hence we catch the exception and ignore it
                re = new Regex(e.Text, RegexOptions.Compiled | RegexOptions.IgnoreCase);
            }
            catch (Exception)
            {
            }
             * */

            //textBox1.BeginInvoke(new ThreadStart(() => textBox1.BackColor = re == null ? Color.Yellow : Color.White));

            searchCache.Clear();
            int idx = 0;
            foreach (ListViewItem i in cache)
            {
                MasterFileTableEntry fn = ((KeyValuePair<ulong, MasterFileTableEntry>)i.Tag).Value;
                if (!string.IsNullOrEmpty(e.Text) && WildcardMatch(fn.Filename, e.Text))
                {
                    searchCache[idx] = i;
                    idx++;
                }
            }

            listView1.BeginInvoke(new ThreadStart(() =>
            {
                listView1.VirtualListSize = idx > 0 ? searchCache.Count : cache.Length;
                UpdateDisplayCounter();
            }));
        }

        void UpdateDisplayCounter()
        {
            toolStripStatusLabel2.Text = string.Format("Displaying {0} files", listView1.VirtualListSize);
        }

        void listView1_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e)
        {

        }

        ListViewItem GetItem(int idx)
        {
            if (searchCache.Count > 0)
                return searchCache[idx];
            else
                return cache[idx];
        }

        void listView1_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
        {
            var lvi = GetItem(e.ItemIndex);
#if true
            string p = lvi.SubItems[1].Text;
            string ext = System.IO.Path.GetExtension(p);
#endif
            e.Item = lvi;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            LoadingDone += new OnLoadingDone(Form1_LoadingDone);
        }

        private void PrepopulateListviewCache()
        {
            // Fastest seems to be to just pre-populate the cache with all items needed
            cache = new ListViewItem[result.Count];

            int i = 0;
            foreach (KeyValuePair<ulong, MasterFileTableEntry> fn in result)
            {
                //string p = mft.GetPath(fn.Value);
                string ext = System.IO.Path.GetExtension(fn.Value.Path);
                var lvi = new ListViewItem(new string[] { fn.Value.Filename, fn.Value.Path }, ext);
                lvi.Tag = fn;

                //if (!imageList1.Images.ContainsKey(ext) && System.IO.File.Exists(p))
                //{
                //    imageList1.Images.Add(ext, Icon.ExtractAssociatedIcon(p));
                //}

                cache[i] = lvi;
                i++;
            }
        }

        void Form1_LoadingDone(Volume mft)
        {
            listView1.BeginInvoke(new ThreadStart(() =>
            {
                listView1.VirtualListSize = result.Count;
                toolStripStatusLabel1.Text = string.Format("Added {0} objects", result.Count);
                toolStripProgressBar1.Style = ProgressBarStyle.Blocks;

                new Thread(() => CompressedSerializer.Write(result, @"res.txt")).Start();
                //s.Write(;
                //s.Read(ref result, @"res.txt");

                //for (int i = 0; i < 100; i += 10)
                //MessageBox.Show("Path: " + mft.GetPath(ref result, result.ElementAt(i).Value));
            }));
        }

        private void toolStripStatusLabel1_Click(object sender, EventArgs e)
        {

        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            load.Abort();
        }

        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            searchTimeout.Enabled = true;
        }

        private void listView1_DoubleClick(object sender, EventArgs e)
        {

        }

        private void Form1_Resize(object sender, EventArgs e)
        {
            listView1.Columns[1].Width = -1;
        }

        private void listView1_ItemActivate(object sender, EventArgs e)
        {
            try
            {
                System.Diagnostics.Process p = new System.Diagnostics.Process();

                p.StartInfo.FileName = GetItem(listView1.SelectedIndices[0]).SubItems[1].Text;
                p.StartInfo.Arguments = "";
                p.StartInfo.UseShellExecute = true;
                p.SynchronizingObject = listView1;

                p.Start();
            }
            catch (Exception) { }
        }
    }

    //public class Timer
    //{
    //    [DllImport("KERNEL32")]
    //    private static extern bool QueryPerformanceCounter(
    //      out long lpPerformanceCount);

    //    [DllImport("Kernel32.dll")]
    //    private static extern bool QueryPerformanceFrequency(out long lpFrequency);

    //    private double start;
    //    private double stop;

    //    public Timer()
    //    {
    //        //if (QueryPerformanceFrequency(out frequency) == false)
    //        //{
    //        //    // Frequency not supported
    //        //    throw new Win32Exception();
    //        //}
    //    }

    //    public void Start()
    //    {
    //        long f, s;
    //        QueryPerformanceFrequency(out f);
    //        QueryPerformanceCounter(out s);

    //        start = (double)s / (double)f;
    //    }

    //    public void Stop()
    //    {
    //        long f, s;
    //        QueryPerformanceFrequency(out f);
    //        QueryPerformanceCounter(out s);

    //        stop = (double)s / (double)f;
    //    }

    //    public double Duration()
    //    {
    //        return ((double)(stop - start));
    //    }
    //}
}
